/*
 * Copyright (c) 2014 pkzd/1.0, 颜文泽(robin)
 * All rights reserved.
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *     1 Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     2 Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include	<asm.h>
#include	<elf.h>
#include	<ino.h>
#include	<term.h>
#include	<param.h>
#include	<direct.h>
#include	<filsys.h>
#include	<string.h>

static ushort port0;
static struct filsys spb;

static int      bread(uint bn, void *buf, uint cnt);
static int      iread(ino_t ino, struct inode *ip);
static int      log_read(uint bn, void *buf, struct inode *ip, uint cnt);
static uint     bmap(struct inode *ip, uint bn);
static char     *have_path(char *path, char *name);
static ino_t    findfile(struct inode *ip, char *name);
static ino_t	namei(char *file, char flag);

static int
bread(uint bn, void *buf, uint cnt)
{
	uint s;
	char *p;

	bn *= 2;
	cnt *= 2;
	s = *(uint *)IDE_SLV;
	while((inb(port0 + 7) & IDE_MASK) != IDE_DRDY)
		;	
	outb(cnt, port0 + 2);
	outb((uchar)bn, port0 + 3);
	outb((uchar)(bn >> 8), port0 + 4);
	outb((uchar)(bn >> 16), port0 + 5);
	outb((uchar)((bn >> 24) | 0xe0 | s), port0 + 6);
	outb(IDE_READ, port0 + 7);
	p = buf;
loop:
	while(inb(port0 + 7) & IDE_BUSY)
		;
	if(inb(port0 + 7) & IDE_ERR)
		return -1;
	if(inb(port0 + 7) & IDE_DRQ){
		insw(p, port0, SECTORSIZ >> 1);
		p += SECTORSIZ;	
		cnt--;
	}
	if(cnt)
		goto loop;
	return 0;
}

static int
iread(ino_t ino, struct inode *ip)
{
	int n;
	char buf[BLKSIZ];
	
	n = NRSVSCT + SPBSIZ + spb.s_ssize + (ino - 1) / NIOPB;
	if(bread(n, buf, 1))
		return -1;
	*ip = *((struct inode *)buf + (ino - 1) % NIOPB);
	return 0;
}

static int
log_read(uint bn, void *buf, struct inode *ip, uint cnt)
{
	register uint i, nb;

	for(i = 0; i < cnt; i++){
		if(nb = bmap(ip, bn + i))
			if(bread(nb, buf + i * BLKSIZ, 1))
				break;
	}
	return i;
}

static uint
bmap(struct inode *ip, uint bn)
{
        uint i, j, nb;
        uint buf[256], buf1[256], buf2[256];

        if(bn < NADDR - 3)
                return ip->i_addr[bn];
        nb = 1;
        bn -= NADDR - 3;
        for(i = 3; i > 0; i--){
                nb <<= 8;
                if(bn < nb)
                        break;
                bn -= nb;
        }
        if(!i)
                return 0;
        j = NADDR - i;
	if(!ip->i_addr[j])
		return 0;
       	nb = ip->i_addr[j];
       	if(bread(nb, buf, 1))
       		return 0;
        switch(i){ 
        case 1:
                memmove(buf2, buf, BLKSIZ);
		if(buf2[bn >> 16])
			return 0;
                nb = buf2[bn >> 16];
		if(bread(nb, buf1, 1))
                        return 0;
                bn &= 0xffff;
        case 2:
		if(i == 2)
			memmove(buf1, buf, BLKSIZ);
		if(!buf1[bn >> 8])
			return 0;
                nb = buf1[bn >> 8];
                bn &= 0xff;
        case 3:
                if(i != 3 && bread(nb, buf, 1))
                        return 0;
                return buf[bn];
        }
        return 0;
}

static char *
have_path(char *path, char *buf)
{
        char *s, *p;

        buf[0] = '\0';
        for(p = path; *p && *p == '/'; p++)
                ;
        if(*p){
                for(s = p; *p && *p != '/'; p++)
                        ;
                memmove(buf, s, p - s >= DIRSIZ ? DIRSIZ : p - s);
                buf[p - s < DIRSIZ ? p - s : DIRSIZ - 1] = '\0';
                while(*p && *p == '/')
                        p++;
        }
        return *p ? p : NULL;
}

static ino_t
findfile(struct inode *ip, char *name)
{
        int i, j, k;
        struct direct dbuf[NDIRENT];

        for(i = 0, j = ip->i_size / BLKSIZ; i <= j; i++){
                if(!log_read(i, dbuf, ip, 1))
                        return 0;
                for(k = 0; k < (i != j ? NDIRENT : (ip->i_size % BLKSIZ) / sizeof(struct direct)); k++){
                        if(dbuf[k].d_ino && strcmp(dbuf[k].d_name, name) == 0)
                                return dbuf[k].d_ino;
                }
        }
        return 0;
}

static ino_t
namei(char *file, char flag)
{
        char *p;
        int ino;
        char buf[DIRSIZ];
        struct inode inode;

        p = file;
        ino = ROOTINO;
        if(*p != '/')
                return 0;
        if(iread(ino, &inode))
                return 0;
	while(p = have_path(p, buf)){
                if(!S_ISDIR(inode.i_mode))
                        return 0;
                if(!(ino = findfile(&inode, buf)))
                        return 0;
                if(iread(ino, &inode))
                        return 0;
        }
        if(!buf[0])
                return ROOTINO;
	if(!S_ISDIR(inode.i_mode))
		return 0;
        switch(flag){
        case 0:
                return findfile(&inode, buf);
	default:
		return 0;
        }
}

void
readkern(void)
{
	ino_t ino;
	uint i, j, n;
	struct elfhd *elf;
	struct elfphd *phd;
	struct inode inode, inode1;

	port0 = *(ushort *)IDE_CMD;
	if(bread(NRSVSCT, &spb, 1))
		mc_spin("Error: can't read super block.\n");
	if(spb.s_magic != FSMAGIC)
		mc_spin("Error: unknow file system.\n");
	if(iread(ROOTINO, &inode))
		mc_spin("Error: cannot read root inode.\n");
	if((ino = namei("/pkzd", 0)) && !iread(ino, &inode1)){
		elf = (struct elfhd *)ELF_ADDR;
		if(log_read(0, elf, &inode1, 1) != 1)
			mc_spin("Error: cannot read kernel's elf header.\n");
        	if(elf->e_magic != ELF_MAGIC)
                	mc_spin("Error: can't read kernel elf header.\n");
        	if(elf->e_type != ET_EXEC)
                	mc_spin("Error: kernel file isn't an executable file.\n");
		phd = (struct elfphd *)((uchar *)elf + elf->e_phoff);
		for(i = 0, j = elf->e_phnum; i < j; i++, phd++){
			if(!phd->e_filesiz)
				continue;
			n = phd->e_filesiz;
			if(log_read(phd->e_offset / BLKSIZ, (void *)phd->e_paddr, &inode1, OBT_BLK_NUM(n)) != OBT_BLK_NUM(n))
				mc_spin("Error: cannot read kernel.\n"); 
			if(phd->e_memsiz < n)
				memset((void *)(phd->e_paddr + n), 0, phd->e_memsiz - n);
		}
	}else
		mc_spin("Error: cannot find kernel file pkzd.\n");
        asm volatile("movl %0, %%eax; jmp *%%eax" : : "r"(elf->e_entry));
}
